package cn.k7g.alloy.utils;

import cn.k7g.alloy.annotation.Label;
import cn.k7g.alloy.exception.AlloyMessageException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.hibernate.validator.internal.engine.ConstraintViolationImpl;
import org.hibernate.validator.internal.engine.path.NodeImpl;
import org.springframework.beans.PropertyAccessException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;

import javax.validation.ConstraintViolation;
import javax.validation.ElementKind;
import javax.validation.Path;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;

/**
 * 项目工具类
 */
@Slf4j
public class AlloyUtils {

    /**
     * 对于json校验中的 增强错误提示转字符串描述内容, 主要对于POST请求
     * @param bindingResult
     * @return
     */
    public static String getErrorToString(BindingResult bindingResult) {
        StringBuilder sb = new StringBuilder();
        for (ObjectError error : bindingResult.getAllErrors()) {
            try {
                if (error.contains( ConstraintViolationImpl.class)) {
                    handleConstraintViolationImpl(error, sb);
                } else if (error.contains( PropertyAccessException.class)) {
                    handlePropertyAccessException(error, sb);
                }
            } catch (Exception ex) {
                log.error("增强错误提示出现错误：", ex);
                sb.append(error.getDefaultMessage()).append("\n");
            }
        }
        return sb.toString();
    }

    private static void handlePropertyAccessException(ObjectError error, StringBuilder sb) {
        PropertyAccessException fe = error.unwrap(PropertyAccessException.class);
        Optional.ofNullable(fe.getPropertyChangeEvent()).ifPresent(pce -> {
            Optional.ofNullable(pce.getSource()).ifPresent(source -> {
                try {
                    sb.append(source.getClass().getAnnotation(Label.class).value());
                } catch (Exception ignored) {
                }

                Class cls = source.getClass();
                String[] fields = pce.getPropertyName().split("\\.");
                for (String fname : fields) {
                    try {
                        Field field = FieldUtils.getField(cls, fname, true);
                        cls = field.getType();
                        sb.append(field.getAnnotation(Label.class).value());
                    } catch (Exception e) {
                        sb.append(fname);
                    }
                    sb.append(" ");
                }

                sb.append("= ").append(pce.getNewValue());
                sb.append(" ").append(extractSimpleException(fe));

                sb.append("\n");
            });
        });
    }

    /**
     * 简化异常消息
     * @param throwable
     * @return
     */
    private static String extractSimpleException(Throwable throwable) {
        if (throwable instanceof AlloyMessageException) {
            return throwable.getMessage();
        }
        if (throwable instanceof ConversionFailedException) {
            return "数据类型不匹配 " + ((ConversionFailedException) throwable).getTargetType().getName();
        }
        if (throwable instanceof TypeMismatchException) {
            return "数据类型不匹配 " + Optional.ofNullable(((TypeMismatchException) throwable).getRequiredType()).map(Class::getName).orElseGet(throwable::getMessage);
        }
        if (throwable.getCause() == null) {
            return throwable.getMessage();
        }
        return extractSimpleException(throwable.getCause());
    }


    private static void handleConstraintViolationImpl(ObjectError error, StringBuilder sb) throws IllegalAccessException {
        ConstraintViolationImpl source = error.unwrap(ConstraintViolationImpl.class);
        Path propertyPath = source.getPropertyPath();
        Iterator<Path.Node> iterator = propertyPath.iterator();
        StringBuilder labelStr = new StringBuilder();
        Object bean = source.getRootBean();
        while (iterator.hasNext()) {
            Path.Node next = iterator.next();
            if (next.getKind() != ElementKind.PROPERTY) {
                continue;
            }

            if (bean instanceof List) {
                bean = ((List<?>) bean).get(next.getIndex());
                labelStr.append("第").append(next.getIndex() + 1).append("行").append(" ");
            } else if (bean instanceof Object[]) {
                bean = ((Object[])bean)[next.getIndex()];
                labelStr.append("第").append(next.getIndex() + 1).append("行").append(" ");
            }
            Field field = FieldUtils.getField(bean.getClass(), next.getName(), true);
            bean = field.get(bean);

            Label label = field.getAnnotation(Label.class);
            if (label != null && label.enhanceVerifyMessage()) {
                labelStr.append(label.value());
            } else {
                labelStr.append(next.getName());
            }
            labelStr.append(" ");
        }
        sb.append(labelStr).append(error.getDefaultMessage()).append("\n");
    }

    /**
     * 对于方法调用时候的触发的参数验证，同样实用于GET请求
     *
     * @param o
     * @return
     */
    public static String getFieldAlias(ConstraintViolation o) {
        Path propertyPath = o.getPropertyPath();
        String fieldName = propertyPath.toString();
        Iterator<Path.Node> iterator = propertyPath.iterator();
        NodeImpl methodNode = null;
        NodeImpl paramNode = null;

        while (iterator.hasNext()) {
            Path.Node node = iterator.next();
            if (node instanceof NodeImpl) {
                if (node.getKind() == ElementKind.METHOD ) {
                    methodNode = (NodeImpl) node;
                } else if (node.getKind() == ElementKind.PARAMETER) {
                    paramNode = (NodeImpl) node;
                }
            }

            if (methodNode != null && paramNode != null) {
                fieldName = getFieldAlias(o, methodNode, paramNode);
            } else {
                fieldName = node.getName();
            }
        }
        return fieldName;
    }

    /**
     * 获取方法中的字段名称，如果有标注 @Label 那么优先使用
     * @return
     */
    private static String getFieldAlias(ConstraintViolation o,  NodeImpl methodNode, NodeImpl paramNode) {
        List<Class<?>> parameterTypes = methodNode.getParameterTypes();
        Class[] types = parameterTypes.toArray(new Class[0]);
        Method method = MethodUtils.getAccessibleMethod(o.getRootBeanClass(), methodNode.getName(), types);
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Annotation[] parameterAnnotation = parameterAnnotations[paramNode.getParameterIndex()];
        for (Annotation annotation : parameterAnnotation) {
            if (annotation instanceof Label) {
                Label label = (Label) annotation;
                if (!label.enhanceVerifyMessage()) {
                    break;
                }
                return label.value();
            }
        }
        return paramNode.getName();
    }
}
